/**
 * @author W
 * @date 2020/8/31 20:50
 */
(function () {
    'use strict';
    const GET_Series = ['get', 'head', 'delete', 'option'];
    const POST_Series = ['post', 'put', 'patch'];
    // 初始化请求拦截器的函数
    let requestInterceptors = [
        function (config) {
            return Promise.resolve(config)
        },
        function (error) {
            return Promise.reject(error)
        }
    ];
    // 初始化响应拦截器的函数
    let responseInterceptors = [
        function (response) {
            return Promise.resolve(response)
        },
        function (error) {
            return Promise.reject(error)
        }
    ];
    let DEFAULTS_CONFIG = null;

    function wajax(config) {
        return new Init(config)
    }

    wajax.prototype = {
        constructor: wajax,
        // 发送请求
        send() {
            let {method,responseType,validateStatus} = this.config;
            return new Promise((resolve, reject) => {
                const _this = this;
                let xhr = new XMLHttpRequest();
                xhr.responseType = responseType;
                xhr.open(method, initRequestUrl.call(this));
                setRequestHeader.call(this,this.config.headers,xhr)
                xhr.onreadystatechange = function () {
                    // 请求成功，但http状态码错误
                    if (!validateStatus(xhr.status)) {
                        reject(dealResponseData(xhr, _this.config, false))
                    }
                    // 请求成功，且状态码符合
                    if (xhr.readyState === 4) {
                        resolve(dealResponseData(xhr, _this.config, true))
                    }
                }
                // 非http状态码错误，如网络中断、请求超时等
                xhr.onerror = function (err) {
                    reject({
                        message: err.message
                    })
                }
                xhr.send(initRequestData.call(this))
                // 响应拦截器，就是在给用户then/catch前先使用then执行两个拦截函数，然后再把then执行后的新promise实例返回给用户接受处理
            }).then(responseInterceptors[0],responseInterceptors[1])
        }
    }

    // 设置请求响应拦截器
    wajax.interceptors = {
        request(resolve,reject) {
            if (typeof resolve === 'function') {
                requestInterceptors[0] = function (config) {
                    return Promise.resolve(resolve(config))
                }
            }
            if (typeof reject === 'function') {
                requestInterceptors[1] = function (error) {
                    return Promise.reject(reject(error))
                }
            }
            return requestInterceptors
        },
        response(resolve,reject) {
            if (typeof resolve === 'function') {
                responseInterceptors[0] = function (response) {
                    return Promise.resolve(resolve(response))
                }
            }
            if (typeof reject === 'function') {
                responseInterceptors[1] = function (error) {
                    return Promise.reject(reject(error))
                }
            }
            return responseInterceptors
        }
    }

    // 处理请求resolve或reject的结果
    function dealResponseData(xhr, config, isResolve) {
        const response = {
            status: xhr.status,
            statusText: xhr.statusText,
            request: xhr,
            headers: getALLResponseHeaders(xhr),
            config: config,
            data:xhr.response
        }
        if (!isResolve) {
            return {
                response,
                message:xhr.statusText
            }
        }
        return response
    }

    // 获取所有的请求响应头
    function getALLResponseHeaders(xhr) {
        const headers = xhr.getAllResponseHeaders().split('\n');
        return headers.reduce((obj, item) => {
            const [key, value] = item.split(/:\s*/g);
            if (key && value) {
                obj[key] = value
            }
            return obj;
        }, {})
    }

    // 设置请求头
    function setRequestHeader(headers,xhr) {
        if (headers && typeof headers === 'object') {
            Object.keys(headers).forEach(key=>{
                xhr.setRequestHeader(key,headers[key])
            })
        }
    }

    // 初始化请求的地址
    function initRequestUrl() {
        let {url: newUrl, baseUrl, params} = this.config;
        // 如果url不是一个绝对地址，并且baseUrl有值的话，就拼接
        if (!/^http(s?):/i.test(newUrl) && baseUrl) {
            newUrl = baseUrl + newUrl;
        }
        const keys = Object.keys(params);
        let paramsStr = '';
        if (keys.length) {
            // 拼接URL上的查询参数，post系列或get系列都可以使用
            keys.forEach(key => {
                paramsStr = `&${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`;
            })
            // 处理有问号和没问号的情况
            newUrl.includes('?') ? newUrl += paramsStr : newUrl += '?' + paramsStr.slice(1);
        }
        return newUrl
    }

    // 初始化请求的主体信息，要区别get系列和post系列
    function initRequestData() {
        let {method, data, transformRequest} = this.config;
        // 如果是get系列请求
        if (GET_Series.includes(method)) return null;

        // post 系列请求
        return transformRequest(data);
    }

    // 添加快捷语法方法,实际上还是调用的wajax方法
    !function initQuick() {
        GET_Series.forEach(name => {
            wajax[name] = function (url, config) {
                config = {
                    method: name,
                    url,
                    ...config
                }
                return wajax(config)
            }
        })

        POST_Series.forEach(name => {
            wajax[name] = function (url, data, config) {
                config = {
                    method: name,
                    url,
                    data,
                    ...config
                }
                return wajax(config)
            }
        })
    }()

    // 初始化 全局默认的配置项
    !function initDefaults() {
        const defaults2type = {
            url: 'string',
            baseUrl: 'string',
            method: 'string',
            responseType: 'string',
            headers: 'object',
            withCredentials: 'boolean',
            timeout: 'number',
            params: 'object',
            data: 'object',
            transformRequest: 'function',
            transformResponse: 'function',
            validateStatus: 'function'
        }
        DEFAULTS_CONFIG = {
            url: "",
            baseUrl: '',
            method: 'get',
            responseType: 'json',
            headers: {},
            withCredentials: false,
            timeout: 0,
            params: {},
            data: {},
            transformRequest: data => JSON.stringify(data),
            transformResponse: data => JSON.parse(data),
            validateStatus: status => status >= 200 && status < 300
        }
        wajax.defaults = new Proxy(DEFAULTS_CONFIG, {
            set(target, p, value, receiver) {
                // 类型校验 url 是必填 其他是选填
                if (p === 'url' && !value) {
                    throw new TypeError(`没有设置请求url`);
                }
                if (typeof value !=='undefined' && defaults2type[p] !== typeof value) {
                    throw new TypeError(`属性${p}的值 ${value} 不是一个 ${defaults2type[p]} 类型`)
                }
                // 如果修改的属性是headers，则合并下，而不是直接覆盖
                if (p === 'headers') {
                    return target[p] = Object.assign(target[p], value)
                }
                return target[p] = value;
            }
        })
    }()

    function Init(config = {}) {
        // 将传入的配置项依次替换自定义的配置项
        // 使用一次次替换可以利用Proxy代理来对类型错误进行监控，同时能同步改变wajax.defaults 和 DEFAULTS_CONFIG，保证了用户传入的config是优先级最高的
        Object.keys(config).forEach(key => {
            wajax.defaults[key] = config[key] || wajax.defaults[key];
        })

        // 执行请求拦截器，用户在请求前可以对配置做进一步的调整
        return requestInterceptors[0](DEFAULTS_CONFIG).then((res)=>{
            // 将最新的配置项暴露给实例
            this.config = res ? res : DEFAULTS_CONFIG;
            // url 是必须配置的
            if (!this.config.url) {
                throw new TypeError(`没有设置请求url`);
            }
            // 得到最新的配置项后就可以发送请求
            return this.send();
        })
    }

    Init.prototype = wajax.prototype;

    if (typeof window !== 'undefined') {
        window.wajax = wajax;
    }
    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
        module.exports = wajax
    }
})()
